package org.springframework.core.annotation;

import org.apache.commons.collections4.CollectionUtils;
import org.springframework.core.annotation.AnnotationTypeMappings.MappingContext;

import java.lang.annotation.Annotation;
import java.lang.annotation.AnnotationTypeMismatchException;
import java.lang.reflect.AnnotatedElement;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;

public class TypeMappedAnnotations
        implements MergedAnnotations {

    private final AnnotatedElement element;
    private final SearchStrategy searchStrategy;
    private final AnnotationFilter filter;

    public TypeMappedAnnotations(AnnotatedElement annotatedElement,
                                 SearchStrategy searchStrategy,
                                 AnnotationFilter filter) {
        this.element = annotatedElement;
        this.searchStrategy = searchStrategy;
        this.filter = filter;
    }

    @Override
    public <A extends Annotation> MergedAnnotation<A> get(Class<A> annotationType) {
        MergedAnnotation<A> result = scan(annotationType, new MergedAnnotationFinder<>());
        return result;
    }

    private <C, R> R scan(C annotationType,
                          AnnotationsProcessor<C, R> processor) {
        return AnnotationsScanner.scan(annotationType, this.element, this.searchStrategy, processor);
    }


    private class MergedAnnotationFinder<A extends Annotation>
            implements AnnotationsProcessor<Object, MergedAnnotation<A>> {

        @Override
        public MergedAnnotation<A> doWithAnnotations(Object annotationType,
                                                     int aggregateIndex,
                                                     Object annotatedElementClass,
                                                     Annotation[] annotations) {

            List<MappingContext> candidates = new ArrayList<>();
            for (Annotation rootAnnotation : annotations) {
                MappingContext mappings = process(annotationType, aggregateIndex, annotatedElementClass, rootAnnotation);
                if (mappings != null) {
                    candidates.add(mappings);
                }
            }

            if (CollectionUtils.isEmpty(candidates)) {
                return null;
            }

            MappingContext mappingContext = candidates.stream().sorted(Comparator.comparingInt(e -> e.getDistance())).findFirst().get();
            return new TypeMappedAnnotation<>(mappingContext);
        }

        private MappingContext process(Object annotationType,
                                       int aggregateIndex,
                                       Object annotatedElementClass,
                                       Annotation rootAnnotation) {
            AnnotationTypeMappings annotationTypeMappings = AnnotationTypeMappings.of(rootAnnotation);
            for (MappingContext mappingContext : annotationTypeMappings.getMappingContextList()) {
                if (mappingContext.isType(annotationType)) {
                    return mappingContext;
                }
            }

            return null;
        }
    }
}
